using System;
using System.Collections.Generic;
using System.Collections;
using System.ComponentModel;
using System.Text;
using System.Runtime.InteropServices;
using System.Linq;
using ESRI.ArcGIS.Geodatabase;
using ESRI.ArcGIS.Carto;
using ESRI.ArcGIS.ArcMap;
using ESRI.ArcGIS.ArcMapUI;
using ESRI.ArcGIS.esriSystem;
using ESRI.ArcGIS.Framework;
using BesAsm.MasterData.Entities;
using BesAsm.MasterData.Services;
using BesAsm.MasterData.Gis;
using BesAsm.MasterData.Data.Bases;
using BesAsm.MasterData.Data;
using BesAsm.Framework.ArcGisUtilities;

namespace BesAsm.MasterData.Gis
{
  /// <summary>
  /// An Entity collection which facilitates interaction between NetTiers entities and ArcGIS features
  /// </summary>
  /// <typeparam name="T"></typeparam>
  [Guid("17e995e7-5740-4460-9e31-ac3b144ea690")]
  [ClassInterface(ClassInterfaceType.None)]
  [ProgId("BesAsm.MasterData.Gis.ArcFeatureList")]
  public class ArcList<T> : TList<T>, IArcList<T> where T : IArcEntity, IEntity
  {
    protected IFeatureLayer _layer;

    protected IApplication _app;
    protected IFeatureLayerSelectionEvents_Event _featureLayerSelectionEvents;
    protected ArcGisUtils _arcUtils;

    #region constructors
    /// <summary>
    /// Initializes a new instance of the <see cref="ArcList&lt;T&gt;"/> class.
    /// </summary>
    /// <param name="app">The app.</param>
    /// <param name="layer">The layer.</param>
    public ArcList(IApplication app, IFeatureLayer layer)
      : this(app, layer, null)
    {
    }

    /// <summary>
    /// Initializes a new instance of the <see cref="ArcList&lt;T&gt;"/> class.
    /// </summary>
    /// <param name="app">The app.</param>
    /// <param name="layer">The layer.</param>
    /// <param name="existingList">The existing list.</param>
    public ArcList(IApplication app, IFeatureLayer layer, IList existingList)
      : this(app, existingList)
    {
      _layer = layer;
      _featureLayerSelectionEvents = (IFeatureLayerSelectionEvents_Event)_layer;
    }

    /// <summary>
    /// Initializes a new instance of the <see cref="ArcList&lt;T&gt;"/> class.
    /// </summary>
    /// <param name="app">The app.</param>
    /// <param name="layerName">Name of the layer.</param>
    public ArcList(IApplication app, string layerName)
      : this(app, layerName, null)
    {
    }

    /// <summary>
    /// Initializes a new instance of the <see cref="ArcList&lt;T&gt;"/> class.
    /// </summary>
    /// <param name="app">The app.</param>
    /// <param name="layerName">Name of the layer.</param>
    /// <param name="existingList">The existing list.</param>
    public ArcList(IApplication app, string layerName, IList existingList)
    {
      _layer = (FeatureLayerClass)_arcUtils.GetFeatureLayer(layerName);
      _featureLayerSelectionEvents = (IFeatureLayerSelectionEvents_Event)_layer;
    }

    /// <summary>
    /// All public constructors should provide a FeatureLayer, or the name of a FeatureLayer.
    /// This private constructor is used only by the public constructors for common initialization.
    /// </summary>
    /// <param name="app"></param>
    /// <param name="existingList"></param>
    private ArcList(IApplication app, IList existingList)
    {
      _app = app;
      _arcUtils = new ArcGisUtils(app);
    }
    #endregion

    public virtual IFeatureLayer Layer
    {
      get { return this._layer; }
      set { _layer = value; }
    }

    private void AddingNewEvent(object sender, AddingNewEventArgs e)
    {
      IFeatureSelection fs = _layer as IFeatureSelection;
      ISelectionSet sel = fs.SelectionSet;

      T t = (T)e.NewObject;
      sel.Add(t.Oid);
    }

    private void ListChangedEvent(object sender, ListChangedEventArgs e)
    {
      IFeatureSelection fs = this as IFeatureSelection;
      ISelectionSet sel = fs.SelectionSet;

      T t = (T)this[e.NewIndex];
      int oid = t.Oid;

      switch (e.ListChangedType)
      {
        case ListChangedType.ItemAdded:
          sel.RemoveList(1, ref oid);
          break;
        case ListChangedType.ItemDeleted:
          sel.Add(oid);
          break;
        case ListChangedType.Reset:
          SyncSelectionToList();
          break;
        default:
          break;
      }
    }

    private void FeatureLayerSelectionChangedEvent()
    {

    }

    /// <summary>
    /// Selects all elements in the underlying feature layer which are present in the underlying TList
    /// </summary>
    /// <param name="clearSelection">Determines whether to clear the currently selected elements before sync</param>
    public void SyncSelectionToList(bool clearSelection)
    {
      UnbindBaseEvents();

      IFeatureSelection fs = _layer as IFeatureSelection;
      ISelectionSet sel = fs.SelectionSet;

      if (clearSelection)
        fs.Clear();

      int[] oids = new int[this.Count];

      for (int i = 0; i < this.Count; i++)
      {
        oids[i] = this[i].Oid;
      }
      sel.AddList(this.Count, ref oids[0]);

      BindBaseEvents();
    }

    /// <summary>
    /// Selects all elements in the underlying feature layer which are present in the underlying TList. Any elements
    /// already selected in the feature layer will be cleared prior to sync.
    /// </summary>
    public void SyncSelectionToList()
    {
      SyncSelectionToList(true);

    }

    /// <summary>
    /// Adds all selected elements in the underlying feature layer to the underlying TList
    /// </summary>
    /// <param name="service">A NetTiers service which implements IArcEntityService; used to retrieve the TList entities</param>
    /// <param name="clearList">Determines whether to clear the TList prior to sync</param>
    public void SyncListToSelection(IArcEntityService<T> service, bool clearList)
    {
      UnbindBaseEvents();
      this.Clear();
      IFeatureSelection fs = _layer as IFeatureSelection;
      ISelectionSet sel = fs.SelectionSet;

      if (clearList)
        this.Clear();

      List<int> oids = _layer.GetSelectedOids().ToList();
      this.AddRange(service.GetByOids(oids));
      
      BindBaseEvents();
    }

    /// <summary>
    /// Adds all selected elements in the underlying feature layer to the underlying TList. The TList will be cleared
    /// prior to sync.
    /// </summary>
    /// <param name="service">A NetTiers service which implements IArcEntityService; used to retrieve the TList entities</param>
    public void SyncListToSelection(IArcEntityService<T> service)
    {
      SyncListToSelection(service, true);
    }

    /// <summary>
    /// Loads all elements in the underlying Feature Layer to the underlying List
    /// </summary>
    /// <param name="service">A NetTiers service which implements IArcEntityService; used to retrieve the TList entities</param>
    public void SyncListToMap(IArcEntityService<T> service)
    {
      UnbindBaseEvents();
      
      List<int> oids = _layer.GetAllOids().ToList();
      this.Clear();
      try
      {      
        TList<T> tlist = service.GetByOids(oids);
        this.AddRange(tlist);
      }
      catch (Exception ex)
      {
        //TODO: Figure out what to do here
        throw ex;
      }
      finally
      {
        BindBaseEvents();
      }
    }

    public List<T> GetSelectedEntities()
    {
      List<int> oids = new List<int>();
      ICursor cursor;

      IFeatureSelection fs = _layer as IFeatureSelection;
      fs.SelectionSet.Search(null, false, out cursor);

      IRow row = cursor.NextRow();
      while (row != null)
      {
        oids.Add(row.OID);
        row = cursor.NextRow();
      }      

      IEnumerable<T> result = this.Where(p => oids.Contains(p.Oid));

      return result.ToList();
    }

    public List<int> GetOids()
    {
      List<int> oids = new List<int>();
      foreach (T entity in this)
      {
        oids.Add(entity.Oid);
      }
      return oids;
    }

    private void BindBaseEvents()
    {
      //TODO: Re-enable this code when needed to allow automatic synchronizing between
      //selected features and contents of TList

      //_tList.AddingNew += new AddingNewEventHandler(AddingNewEvent);
      //_tList.ListChanged += new ListChangedEventHandler(ListChangedEvent);

      _featureLayerSelectionEvents.FeatureLayerSelectionChanged +=
        new IFeatureLayerSelectionEvents_FeatureLayerSelectionChangedEventHandler
          (FeatureLayerSelectionChangedEvent);
    }

    private void UnbindBaseEvents()
    {
      //TODO: Re-enable this code when needed to allow automatic synchronizing between
      //selected features and contents of TList

      //_tList.AddingNew -= new AddingNewEventHandler(AddingNewEvent);
      //_tList.ListChanged -= new ListChangedEventHandler(ListChangedEvent);

      _featureLayerSelectionEvents.FeatureLayerSelectionChanged -=
        new IFeatureLayerSelectionEvents_FeatureLayerSelectionChangedEventHandler
          (FeatureLayerSelectionChangedEvent);
    }

  }
}
